using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

using SqInterface.Utils;

namespace SqInterface.InteropServices
{
	/// <summary>
	/// Emits custom descendants of the <see cref="MulticastDelegate"/> type and creates instances of them.
	/// </summary>
	public class DelegateGenerator
	{
		#region Private fields

		private static object _syncRoot = new object();

		private static AssemblyBuilder _assembly;

		private static ModuleBuilder _module;

		private static Hashtable _types = Hashtable.Synchronized( new Hashtable() );

		#endregion

		#region Private methods

		/// <summary>
		/// Emits <see cref="MulticastDelegate"/> descendant suitable for specified method and specified <see cref="CallingConvention"/>.
		/// </summary>
		/// <param name="method">Method to emit delegate class for.</param>
		/// <param name="typeName">The name of the emitted class.</param>
		/// <param name="callingConvention">Calling convention.</param>
		/// <returns>The emitted type.</returns>
		private static Type Generate( MethodInfo method, string typeName, CallingConvention callingConvention )
		{
			Type callingConventionType;

			switch( callingConvention )
			{
				case CallingConvention.Cdecl:
					callingConventionType = typeof( System.Runtime.CompilerServices.CallConvCdecl );
					break;
				case CallingConvention.FastCall:
					callingConventionType = typeof( System.Runtime.CompilerServices.CallConvFastcall );
					break;
				case CallingConvention.StdCall:
					callingConventionType = typeof( System.Runtime.CompilerServices.CallConvStdcall );
					break;
				case CallingConvention.ThisCall:
					callingConventionType = typeof( System.Runtime.CompilerServices.CallConvThiscall );
					break;
				default:
					throw new ArgumentOutOfRangeException( "callingConvention", callingConvention, "Unknown calling convention." );
			}

			lock( _syncRoot )
			{
				Type resultType = (Type)_types[ typeName ];

				if( resultType != null )
				{
					return resultType;
				}

				if( _assembly == null )
				{
					AssemblyName name = new AssemblyName();

					name.Name = "CustomDelegatesAssembly";

					_assembly = AppDomain.CurrentDomain.DefineDynamicAssembly( name, AssemblyBuilderAccess.RunAndSave );

					_module = _assembly.DefineDynamicModule( "CustomDelegatesAssembly.dll", "CustomDelegatesAssembly.dll", true );
				}

				TypeBuilder type = _module.DefineType( 
					typeName,
					TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.AutoClass | TypeAttributes.AnsiClass,
					typeof( System.MulticastDelegate )
					);

				// Constructor

				ConstructorBuilder c = type.DefineConstructor( MethodAttributes.HideBySig | MethodAttributes.Public, CallingConventions.Standard, new Type[]{ typeof( object ), typeof( IntPtr ) } );

				c.SetImplementationFlags( MethodImplAttributes.Managed | MethodImplAttributes.Runtime );

				// "Invoke" method

				ParameterInfo[] parameters = method.GetParameters();

				Type[] parameterTypes = new Type[ parameters.Length ];

				for( int i = 0; i < parameterTypes.Length; i++ )
				{
					parameterTypes[ i ] = parameters[ i ].ParameterType;
				}

				/*
				public MethodBuilder System.Reflection.Emit.TypeBuilder.DefineMethod(string name, MethodAttributes attributes, CallingConventions callingConvention, Type returnType, Type[] parameterTypes)
				{
					MethodBuilder builder2;
					try
					{
							int num1;
							this.Enter();
							if (name == null)
							{
								throw new ArgumentNullException("name");
							}
							if (name.Length == 0)
							{
								throw new ArgumentException(Environment.GetResourceString("Argument_EmptyName"), "name");
							}
							this.ThrowIfCreated();
							if ((!this.m_isHiddenGlobalType && ((this.m_iAttr & TypeAttributes.ClassSemanticsMask) == TypeAttributes.ClassSemanticsMask)) && ((attributes & MethodAttributes.Abstract) == MethodAttributes.ReuseSlot))
							{
								throw new ArgumentException(Environment.GetResourceString("Argument_BadAttributeOnInterfaceMethod"));
							}
							MethodBuilder builder1 = new MethodBuilder(name, attributes, callingConvention, returnType, parameterTypes, this.m_module, this, false);
							byte[] buffer1 = builder1.GetMethodSignature().InternalGetSignature(out num1);
							if ((!this.m_isHiddenGlobalType && ((builder1.Attributes & MethodAttributes.SpecialName) != MethodAttributes.ReuseSlot)) && builder1.Name.Equals(ConstructorInfo.ConstructorName))
							{
								this.m_constructorCount++;
							}
							this.m_listMethods.Add(builder1);
							MethodToken token1 = TypeBuilder.InternalDefineMethod(this.m_tdType, name, buffer1, num1, builder1.Attributes, this.m_module);
							builder1.SetToken(token1);
							builder2 = builder1;
					}
					finally
					{
							this.Exit();
					}
					return builder2;
				}
				*/

				/*
				MethodBuilder m = type.DefineMethod( 
					"Invoke", 
					MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Virtual, 
					CallingConventions.Standard, 
					method.ReturnType, 
					parameterTypes 
					);
				*/

				Type typeBuilder = type.GetType();

				MethodBuilder m;

				try
				{
					Reflector.InvokeMethod( typeBuilder, "Enter", type );

					Reflector.InvokeMethod( typeBuilder, "ThrowIfCreated", type );

					m = (MethodBuilder)Reflector.CreateInstance( 
						typeof( MethodBuilder ),
						"Invoke",
						MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Virtual, 
						CallingConventions.Standard, 
						method.ReturnType, 
						parameterTypes,
						_module,
						type,
						false
						);

					// Get signature

					object o = Reflector.InvokeMethod( m.GetType(), "GetMethodSignature", m );

					/*
					internal byte[] InternalGetSignature(out int length)
					{
						if (!this.bSigDone)
						{
								this.bSigDone = true;
								this.SetNumberOfSignatureElements(false);
						}
						length = this.m_currSig;
						return this.m_signature;
					}
					*/

					Reflector.SetField( o.GetType(), "bSigDone", o, true );

					Reflector.InvokeMethod( o.GetType(), "SetNumberOfSignatureElements", o, false );

					byte[] b = (byte[])Reflector.GetField( o.GetType(), "m_signature", o );

					int size = (int)Reflector.GetField( o.GetType(), "m_currSig", o );

					// Hack signature

				{
					TypeToken token = _module.GetTypeToken( callingConventionType );

					// Compress
					byte typeRefToken = (byte)((token.Token >> 24) | ((token.Token & 0xFF) << 2));

					byte[] a = new byte[ size + 2 ];

					a[ 0 ] = b[ 0 ];
					a[ 1 ] = b[ 1 ];
					a[ 2 ] = 0x20; // modopt (0x1f is modreq)
					a[ 3 ] = typeRefToken; // type def or type ref token

					Array.Copy( b, 2, a, 4, size - 2 );

					b = a;

					size++;
					size++;
				}

					((ArrayList)Reflector.GetField( typeBuilder, "m_listMethods", type )).Add( m );

					o = Reflector.InvokeStaticMethod( 
						typeBuilder,
						"InternalDefineMethod",
						Reflector.GetField( typeBuilder, "m_tdType", type ),
						"Invoke",
						b,
						size,
						m.Attributes,
						_module
						);

					Reflector.InvokeMethod( m.GetType(), "SetToken", m, o );
				}
				finally
				{
					Reflector.InvokeMethod( typeBuilder, "Exit", type );
				}

				m.SetImplementationFlags( MethodImplAttributes.Managed | MethodImplAttributes.Runtime );

				foreach( ParameterInfo parameter in parameters )
				{
					ParameterBuilder p = m.DefineParameter( parameter.Position + 1, parameter.Attributes, parameter.Name );

					if( (parameter.Attributes & ParameterAttributes.HasDefault) != 0 )
					{
						p.SetConstant( parameter.DefaultValue );
					}

					// Marshaling: not implemeted

					// Custom attributes: should not be implemented
				}

				// "BeginInvoke" method

				Type[] asyncParameterTypes = new Type[ parameterTypes.Length + 2 ];

				Array.Copy( parameterTypes, asyncParameterTypes, parameterTypes.Length );

				asyncParameterTypes[ parameterTypes.Length ] = typeof( System.AsyncCallback );

				asyncParameterTypes[ parameterTypes.Length + 1 ] = typeof( object );

				m = type.DefineMethod( "BeginInvoke", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot, CallingConventions.Standard, typeof( System.IAsyncResult ), asyncParameterTypes );

				m.SetImplementationFlags( MethodImplAttributes.Managed | MethodImplAttributes.Runtime );

				foreach( ParameterInfo parameter in parameters )
				{
					ParameterBuilder p = m.DefineParameter( parameter.Position + 1, parameter.Attributes, parameter.Name );

					if( (parameter.Attributes & ParameterAttributes.HasDefault) != 0 )
					{
						p.SetConstant( parameter.DefaultValue );
					}

					// Marshaling: not implemeted

					// Custom attributes: should not be implemented
				}

				// "EndInvoke" method

				m = type.DefineMethod( "EndInvoke", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot, CallingConventions.Standard, method.ReturnType, new Type[]{ typeof( System.IAsyncResult ) } );

				m.SetImplementationFlags( MethodImplAttributes.Managed | MethodImplAttributes.Runtime );

				/* Save the emitted type */

				resultType = type.CreateType();

				_types[ typeName ] = resultType;

				// Debug
				// _assembly.Save( _module.Name );

				return resultType;
			}
		}

		/// <summary>
		/// Suggest name for delegate type by specified <see cref="MethodInfo"/> and <see cref="CallingConvention"/>.
		/// </summary>
		/// <param name="method"></param>
		/// <param name="callingConvention"></param>
		/// <returns></returns>
		private static string GetTypeFullName( MethodInfo method, CallingConvention callingConvention )
		{
			return "CustomDelegates" + Type.Delimiter.ToString() + method.DeclaringType.FullName + "_" + method.Name + "_" + callingConvention.ToString();
		}

		#endregion

		#region Public methods

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/> with <see cref="CallingConvention.Cdecl"/> calling convention.
		/// </summary>
		/// <param name="obj">The object on which method is defined (null for static methods).</param>
		/// <param name="methodName">The name of the method.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( object obj, string methodName )
		{
			return CreateDelegate( obj, methodName, CallingConvention.Cdecl );
		}

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/>.
		/// </summary>
		/// <param name="obj">The object on which method is defined (null for static methods).</param>
		/// <param name="methodName">The name of the method.</param>
		/// <param name="callingConvention"><see cref="CallingConvention"/> of the delegate's "Invoke" method.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( object obj, string methodName, CallingConvention callingConvention )
		{
			if( obj == null )
			{
				throw new ArgumentNullException( "obj" );
			}

			MethodInfo method = obj.GetType().GetMethod( methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static );

			if( method == null )
			{
				throw new ArgumentOutOfRangeException( "method", method, "The specified method was not found." );
			}

			return CreateDelegate( obj, method, callingConvention );
		}

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/> with <see cref="CallingConvention.Cdecl"/> calling convention.
		/// </summary>
		/// <param name="type">The type of object on which method is defined.</param>
		/// <param name="methodName">The name of the method.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( Type type, string methodName )
		{
			return CreateDelegate( type, methodName, CallingConvention.Cdecl );
		}

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/>.
		/// </summary>
		/// <param name="type">The type of object on which method is defined.</param>
		/// <param name="methodName">The name of the method.</param>
		/// <param name="callingConvention"><see cref="CallingConvention"/> of the delegate's "Invoke" method.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( Type type, string methodName, CallingConvention callingConvention )
		{
			if( type == null )
			{
				throw new ArgumentNullException( "type" );
			}

			MethodInfo method = type.GetMethod( methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static );

			if( method == null )
			{
				throw new ArgumentOutOfRangeException( "method", method, "The specified method was not found." );
			}

			return CreateDelegate( null, method, callingConvention );
		}

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/> with <see cref="CallingConvention.Cdecl"/> calling convention.
		/// </summary>
		/// <param name="del">Delegate to obtain target object and <see cref="MethodInfo"/> from.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( Delegate del )
		{
			return CreateDelegate( del, CallingConvention.Cdecl );
		}

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/>.
		/// </summary>
		/// <param name="del">Delegate to obtain target object and <see cref="MethodInfo"/> from.</param>
		/// <param name="callingConvention"><see cref="CallingConvention"/> of the delegate's "Invoke" method.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( Delegate del, CallingConvention callingConvention )
		{
			if( del == null )
			{
				throw new ArgumentNullException( "del" );
			}

			return CreateDelegate( del.Target, del.Method, callingConvention );
		}

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/> with <see cref="CallingConvention.Cdecl"/> calling convention.
		/// </summary>
		/// <param name="obj">The object on which method is defined (null for static methods).</param>
		/// <param name="method">The <see cref="MethodInfo"/> of the method for which a delegate is created.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( object obj, MethodInfo method )
		{
			return CreateDelegate( obj, method, CallingConvention.Cdecl );
		}

		/// <summary>
		/// Creates <see cref="MulticastDelegate"/>.
		/// </summary>
		/// <param name="obj">The object on which method is defined (null for static methods).</param>
		/// <param name="method">The <see cref="MethodInfo"/> of the method for which a delegate is created.</param>
		/// <param name="callingConvention"><see cref="CallingConvention"/> of the delegate's "Invoke" method.</param>
		/// <returns>Instance of <see cref="MulticastDelegate"/> descendant.</returns>
		/// <remarks>This method emits <see cref="MulticastDelegate"/> descending class (if not already emitted) suitable
		/// for the specified <see cref="MethodInfo"/>.</remarks>
		public static MulticastDelegate CreateDelegate( object obj, MethodInfo method, CallingConvention callingConvention )
		{
			if( method == null )
			{
				throw new ArgumentNullException( "method" );
			}

			string typeName = GetTypeFullName( method, callingConvention );

			Type type = (Type)_types[ typeName ];

			if( type == null )
			{
				type = Generate( method, typeName, callingConvention );
			}

			return (MulticastDelegate)Activator.CreateInstance( type, new object[]{ obj, method.MethodHandle.GetFunctionPointer() } );
		}

		#endregion
	}
}
